Skip to content

fix(sdk): propagate connector-discovered status to the model row#23

Merged
vigneshnarayanaswamy merged 1 commit into
mainfrom
vigneshn/status-propagation
Jun 12, 2026
Merged

fix(sdk): propagate connector-discovered status to the model row#23
vigneshnarayanaswamy merged 1 commit into
mainfrom
vigneshn/status-propagation

Conversation

@vigneshnarayanaswamy

Copy link
Copy Markdown
Collaborator

Problem

Connectors deriving lifecycle status from sources couldn't propagate it to the model registry. Ledger.add() called register() without a status (defaulting to active), and register() early-returns cached/existing refs unchanged — add() then only bumped last_seen before update_model(). So a connector that derives status='deprecated' for an entity deleted at the source saw it land in snapshot payloads only; the model row's status stayed active forever.

Fix

Ledger.add() now reads node.metadata['status'] — the same contract add() already uses for owner / model_type / tier / purpose / model_origin, and what the SQL connector's unmapped-columns path already populates (no DataNode / ModelRef API change):

  • New models: the discovered status is passed to register().
  • Existing models: ref.status is reassigned when the discovered status differs, before the content-hash dedup check — so the row self-corrects even when the snapshot write is skipped as unchanged.
  • Guard: values are validated against ModelStatus (case-insensitive) and normalized; an absent or unrecognized status leaves the stored status untouched, so an explicit status is never regressed to the default.

Both Snowflake flush MERGE paths already SET STATUS on match, so existing rows self-correct on the next sync — no migration needed.

Tests

  • SDK-level add() coverage: new-model-with-status, existing-model flip, default, absent/unknown no-op, case normalization, flip-records-snapshot, dedup-skip self-correction (tests/test_graph/test_ledger_graph.py)
  • Snowflake MERGE-path tests driving Ledger.add() end-to-end, plus in-memory ↔ Snowflake-SQL parity on the same discovery sequence (tests/test_backends/test_snowflake_ledger.py; the mock session now captures PURPOSE/STATUS from the MERGE source instead of hardcoding them)
  • Hypothesis property: a model's status equals the last valid discovered status, with absent/unknown values never regressing it (tests/test_invariants.py)

750 passed locally; ruff, mypy, and the coverage gate are clean.

🤖 Generated with Claude Code

Ledger.add() never carried a discovered node's status into the models
table: register() was called without status (defaulting to 'active') and
early-returns cached refs unchanged, while add() only bumped last_seen
before update_model(). A connector deriving a lifecycle status from the
source (e.g. 'deprecated' for an entity deleted upstream) saw it land in
snapshot payloads only — the model row stayed 'active' forever.

add() now reads node.metadata['status'] (the same contract as owner/
model_type/tier/purpose/model_origin), passes it to register() for new
models, and assigns ref.status on existing refs when the discovered
status differs, before the content-hash dedup check — so the row
self-corrects even when the snapshot write is skipped as unchanged.
Both Snowflake flush MERGE paths already SET STATUS on match, so
existing rows converge on the next sync with no migration.

Statuses are validated against ModelStatus (case-insensitive) and
normalized; absent or unrecognized values leave the stored status
untouched, so an explicit status is never regressed to the default.

Tests: SDK-level add() coverage (new-model-with-status, existing-model
flip, absent/unknown no-op, dedup-skip self-correction), Snowflake
MERGE-path tests with in-memory parity, and a Hypothesis property
(status equals the last valid discovered status).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vigneshnarayanaswamy vigneshnarayanaswamy merged commit 0b2e85c into main Jun 12, 2026
10 checks passed
@vigneshnarayanaswamy vigneshnarayanaswamy deleted the vigneshn/status-propagation branch June 12, 2026 07:48

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b05f1e9bdf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

added = 0
skipped = 0
for node in nodes:
node_status = _normalize_status(node.metadata.get("status"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Accept status metadata case-insensitively

When status comes from sql_connector without an explicit metadata_columns mapping and the DB driver returns uppercase column names (for example Snowflake-style rows with STATUS), the connector preserves the original key in node.metadata (src/model_ledger/connectors/sql.py lines 237-240). This lookup only checks lowercase status, so the new propagation path is skipped and the model row remains active even though the discovered snapshot carries the source status. Consider normalizing metadata keys or looking up status case-insensitively here.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant